package net.helix.core.loader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Scanner;

import net.helix.core.Face;
import net.helix.core.Normal;
import net.helix.core.UV;
import net.helix.core.Vertex;
import net.helix.core.geometry.Geometry_Image;
import net.helix.core.geometry.Geometry_Model;
import net.helix.core.geometry.Geometry_Material;
import net.helix.core.geometry.Geometry_Mesh;
import net.helix.element.Element_Mesh;
import net.helix.engine.Engine;
import net.helix.engine.library.items.LibraryItem_Mesh;
import net.helix.engine.library.items.LibraryItem_Model;
import net.helix.ui.panels.library.LibraryTreeTableModel;


/**
 * .obj file loader. Final output is a Geometry containing
 * all of the vertex, uv, and normals. Data for faces and
 * Materials belong in each Mesh associated with this model.
 * To translate .obj files to the Helix type structure, a
 * Geometry acts like a movieclip with each mesh added as a
 * layer inside.
 * 
 * @author Runouw
 * @see net.helix.core.loader.MTLLoader
 * @see net.helix.core.geometry.Geometry_Model
 */
public class OBJLoader extends ModelLoader {
	private Geometry_Mesh currentMesh = null;
	private MTLLoader mtl = null;
	
	private ArrayList<LoaderMesh> loadedMeshes;
	
	/**
	 * Attempts to load the model, also loading all .mtl files
	 * required by this file.
	 * @param engine - The engine. Here so this class can access
	 * helper functions to load images.
	 * @param treeModel - The treeModel it adds the library items
	 * to. Leave it as null if you don't want it to add to the library.
	 * @param addr - The address of where to load the .obj from
	 */
	public OBJLoader(Engine engine, LibraryTreeTableModel treeModel, String addr){
		
		// Use Engine to get the last selected viewPath, add it
		// to stage (if it can). And use Engine to get the
		// library it adds everything to.
		// All materials should be added to a folder next to
		// the Geometry file created from this. Same with all
		// of the meshes.
		
		super(engine, treeModel, addr);
		System.out.println("Load obj "+addr);
		
		loadedMeshes = new ArrayList<LoaderMesh>();
		currentMesh = null;
		mtl = null;
		
		try {
			FileInputStream fin = new FileInputStream(file);
			
			InputStreamReader finr = new InputStreamReader(fin, "UTF-8");
			
			Scanner scanner = new Scanner(finr);
			while(scanner.hasNextLine()){
				readLine(scanner.nextLine());
			}
			
			scanner.close();
			//finr.close();
			//fin.close();
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// TODO: only shade under certain circumstances...
		for(int i=0;i<loadedMeshes.size();i++){
			loadedMeshes.get(i).mesh.setShadingSmooth(this, loadedMeshes.get(i).material);
		}
		
		for(int i=0;i<loadedMeshes.size();i++){
			loadedMeshes.get(i).mesh.prepare(this);
		}
		
		if(treeModel != null){
			LibraryItem_Model newModel = new LibraryItem_Model(treeModel.getLibrary(), file.getName());
			
			ArrayList<Element_Mesh> meshElements = new ArrayList<Element_Mesh>();
			
			for(int i=0;i<loadedMeshes.size();i++){
				LibraryItem_Mesh newMesh = new LibraryItem_Mesh(treeModel.getLibrary(), "mesh "+(i+1));
				
				newMesh.setMaterial(loadedMeshes.get(i).material);
				newMesh.setMesh(loadedMeshes.get(i).mesh);
				
				newMesh.setModel(this);
				
				Engine.addLoadingText("Adding "+newMesh.name + ".");
				treeModel.addNewItem(newMesh);
				
				
				Element_Mesh newMeshElement = new Element_Mesh(newMesh);
				
				meshElements.add(newMeshElement);
			}
			
			newModel.setMeshes(meshElements);
			
			treeModel.addNewItem(newModel);
			
		}
		
		
	}
	private String removeComments(String line){
		if(line.contains("#")){
			return line.substring(0, line.indexOf('#'));
		}else{
			return line;
		}
	}
	private void readLine(String line){
		line = removeComments(line);
		// skips line if it is determined a comment
		// checks the beginning of each line (v, vt, vn, f, ect) and calls a function to handle it
		
		for(int i=0;i<line.length();i++){
			try{
			
				if(line.charAt(i) == ' '){
					continue;
				}else if(line.charAt(i) == '\t'){
					continue;
				}else if(line.charAt(i) == '#'){
					// comment, time to get out of here...
					return;
				}else if(i + 1 >= line.length()){
					// reached end, no charAt(i+1)...
					break;
				}else if(line.charAt(i) == 'v' && line.charAt(i+1) == ' '){
					v(line.substring(i+1));
					
					return;
				}else if(line.charAt(i) == 'v' && line.charAt(i+1) == 't' && line.charAt(i+2) == ' '){
					vt(line.substring(i+2));
					
					return;
				}else if(line.charAt(i) == 'v' && line.charAt(i+1) == 'n' && line.charAt(i+2) == ' '){
					vn(line.substring(i+2));
					
					return;
				}else if(line.charAt(i) == 'f' && line.charAt(i+1) == ' '){
					f(line.substring(i+1));
					
					return;
				}else if(i + 6 < line.length() && line.charAt(i) == 'u' && line.charAt(i+1) == 's' && line.charAt(i+2) == 'e' && line.charAt(i+3) == 'm' && line.charAt(i+4) == 't' && line.charAt(i+5) == 'l' && line.charAt(i+6) == ' '){
					usemtl(line.substring(i+7));
					
					return;
				}else if(i + 6 < line.length() && line.charAt(i) == 'm' && line.charAt(i+1) == 't' && line.charAt(i+2) == 'l' && line.charAt(i+3) == 'l' && line.charAt(i+4) == 'i' && line.charAt(i+5) == 'b' && line.charAt(i+6) == ' '){
					mtllib(line.substring(i+7));
					
					return;
				}else{
					break;
				}
			
			}catch(Exception e){
				System.out.println("error found on line: "+line);
				e.printStackTrace();
			}
		}
		
	}
	private void v(String line){
		line = removeComments(line);
		
		String[] arr = line.split(" ");
		
		float x = 0;
		float y = 0;
		float z = 0;
		
		int h = 0;
		for(int i=0;i<arr.length;i++){
			if(!arr[i].equals("")){
				h++;
				switch(h){
					case 1:
						x = Float.parseFloat(arr[i]);
						break;
					case 2:
						y = Float.parseFloat(arr[i]);
						break;
					case 3:
						z = Float.parseFloat(arr[i]);
						break;
					default:
						break;
				
				}
			}
		}
		
		vertices.add(new Vertex(x, y, z));
		
		
	}
	private void vt(String line){
		line = removeComments(line);
		
		String[] arr = line.split(" ");
		
		float uvx = 0;
		float uvy = 0;
		
		int h = 0;
		for(int i=0;i<arr.length;i++){
			if(!arr[i].equals("")){
				
				h++;
				switch(h){
					case 1:
						uvx = Float.parseFloat(arr[i]);
						break;
					case 2:
						uvy = Float.parseFloat(arr[i]);
						break;
					default:
						break;
				}
			}
		}
		
		uvs.add(new UV(uvx, uvy));
		
	}
	private void vn(String line){
		line = removeComments(line);
		
		String[] arr = line.split(" ");
		
		float x = 0;
		float y = 0;
		float z = 0;
		
		int h = 0;
		for(int i=0;i<arr.length;i++){
			if(!arr[i].equals("")){
				h++;
				switch(h){
				case 1:
					x = Float.parseFloat(arr[i]);
					break;
				case 2:
					y = Float.parseFloat(arr[i]);
					break;
				case 3:
					z = Float.parseFloat(arr[i]);
					break;
				default:
					break;
				}
			}
		}
		
		normals.add(new Normal(x, y, z));
		
		
	}
	private void f(String line){
		if(currentMesh == null){
			usemtl(null);
		}
		
		String[] arr = line.split(" ");
		
		int v1 = Integer.MIN_VALUE;
		int v2 = Integer.MIN_VALUE;
		int v3 = Integer.MIN_VALUE;
		int v4 = Integer.MIN_VALUE;
		
		int vt1 = Integer.MIN_VALUE;
		int vt2 = Integer.MIN_VALUE;
		int vt3 = Integer.MIN_VALUE;
		int vt4 = Integer.MIN_VALUE;
		
		int vn1 = Integer.MIN_VALUE;
		int vn2 = Integer.MIN_VALUE;
		int vn3 = Integer.MIN_VALUE;
		int vn4 = Integer.MIN_VALUE;
		
		int h = 0;
		for(int i=0;i<arr.length;i++){
			if(!arr[i].equals("")){
				h++;
				
				int v = 0;
				int vt = 0;
				int vn = 0;
				if(i > 0){
					String[] arr2 = arr[i].split("/");
					if(arr2.length > 0 && arr2[0].length() > 0){
						v = Integer.parseInt(arr2[0]);
					}
					if(arr2.length > 1 && arr2[1].length() > 0){
						vt = Integer.parseInt(arr2[1]);
					}
					if(arr2.length > 2 && arr2[2].length() > 0){
						vn = Integer.parseInt(arr2[2]);
					}
					
					if(v != Integer.MIN_VALUE){
						if(v < 0){
							v = vertices.size()+v;
						}else{
							v--;
						}
					}
					
					if(vt != Integer.MIN_VALUE){
						if(vt < 0){
							vt = uvs.size()+vt;
						}else{
							vt--;
						}
					}
					
					if(vn != Integer.MIN_VALUE){
						if(vn < 0){
							vn = normals.size()+vn;
						}else{
							vn--;
						}
					}
					
					
					
				}
				
				
				
				
				
				switch(h){
					case 1:
						v1 = v;
						vt1 = vt;
						vn1 = vn;
						break;
					case 2:
						v2 = v;
						vt2 = vt;
						vn2 = vn;
						break;
					case 3:
						v3 = v;
						vt3 = vt;
						vn3 = vn;
						break;
					case 4:
						v4 = v;
						vt4 = vt;
						vn4 = vn;
						break;
					default:
						break;
				
				}
			}
		}
		
		if(arr.length == 5){
			// quad, just add 2 triangles to fix this...
			currentMesh.getFaces().add(new Face(v1, v2, v3, vt1, vt2, vt3, vn1, vn2, vn3));
			currentMesh.getFaces().add(new Face(v1, v3, v4, vt1, vt3, vt4, vn1, vn3, vn4));
		}else if(arr.length == 4){
			// triangle
			currentMesh.getFaces().add(new Face(v1, v2, v3, vt1, vt2, vt3, vn1, vn2, vn3));
		}
		
	}
	public void usemtl(String line){
		line = removeComments(line);
		
		LoaderMesh loaderMesh = new LoaderMesh();
		loaderMesh.mesh = new Geometry_Mesh();
		if(line == null){
			// set no texture 
			loaderMesh.material = null;
		}else{
			// make sure texture in mtl is loaded, and set it to the mesh...
			if(mtl != null){
				loaderMesh.material = mtl.getMaterial(line);
			}
			
			if(loaderMesh.material == null){
				loaderMesh.material = new Geometry_Material();
				
				String addr = file.getParent() + File.separator + line + ".png";
				
				try{
					Geometry_Image image = engine.loadImageAndAddToLibrary(treeModel, addr);
					loaderMesh.material.setTextureKd(image);
				}catch(Exception e){
					
				}
				
			}
			
			
		}
		
		/*
		mesh = vertices;
		mesh.uvs = uvs;
		mesh.normals = normals;
		*/
		//loaderMesh.mesh.setGeometry(this);
		
		currentMesh = loaderMesh.mesh;
		
		
		loadedMeshes.add(0, loaderMesh);
		//vertices = new ArrayList<Vertex>();
		//uvs = new ArrayList<UV>();
		//normals = new ArrayList<Normal>();
		
	}
	public void mtllib(String line) {
		line = removeComments(line);
		
		String addr = file.getParent() + File.separator + line;

		mtl = new MTLLoader(engine, treeModel, addr);
	}
	class LoaderMesh {
		Geometry_Mesh mesh;
		Geometry_Material material;
	}
	
}
